android Tween动画原理

Android提供了几种基本的动画:

  1. 帧动画
  2. 补间动画
  3. 属性动画

其中属性动画和补间动画比较常用,它们最大的区别在于补间动画并不会真正的改变View的属性,什么意思呢?比如通过补间动画将页面中的Button从左边移动到右边一段距离,如果此时点击Button,它不会对点击事件做出响应,这是因为button的作用区域(点击该区域依然可以触发click事件)依然在原来的位置,这是补间动画将其绘制在其原来的右边罢了。

本篇将首先从源码的角度对补间动画进行分析,属性动画会在下一篇中做介绍,至于补间动画的基本用法,这里就不多做介绍了。
在我们分析之前我们先提两个问题:

  1. 动画是如何进行绘制的?
  2. 动画是怎么计算每一帧的画面的?即某一个时刻View的位置的?

带着这两个问题,我们开始分析补间动画,这里首先从startAnimation开始分析,这是动画开始执行的方法。

1
2
3
4
5
6
7
//frameworks\base\core\java\android\view\View.java
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}

startAnimation中主要做了两件事,第一个就是通过setAnimation设置当前View的动画,然后通过invalidate重绘制View。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public void setAnimation(Animation animation) {
mCurrentAnimation = animation;

if (animation != null) {
// If the screen is off assume the animation start time is now instead of
// the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
// would cause the animation to start when the screen turns back on
if (mAttachInfo != null && !mAttachInfo.mScreenOn &&
animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
}
animation.reset();
}
}

setAnimation将animation保存在View的mCurrentAnimation以备使用。
接下来就是通过invalidate来重绘制View,这个我在专门的一篇中有所介绍,这里我简单说一下, invalidate实际上会从当前发起绘制的view开始向上寻找父View(ViewParent),然后和其父View计算总的脏区域,父View再计算根据得到的脏区域计算和其父View的脏区域,这个操作一直到ViewRootImpl,即View层级树的顶部管理者,它会根据计算的脏区域触发Draw操作。也就是会走到performTraversals中,这个流程想必大家都清楚了。

View在绘制完自身的内容后,会通过dispatchDrawl来绘制其子View,动画也就是在此时进行绘制的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// frameworks/base/core/java/android/view/ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
final int count = mChildrenCount;//子view的个数
final View[] children = mChildren;//子view节点
int flags = mGroupFlags;

……
boolean more = false;
final long drawingTime = getDrawingTime();

if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);//绘制子view
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}

……
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

dispatchDraw中会通过遍历孩子节点并未其调用drawChild来绘制子View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
……
Transformation transformToApply = null;

final Animation a = getAnimation();
if (a != null) {
more = drawAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
……
if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() ||
(mPrivateFlags3& PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;

if (offsetForScroll) {
transX = -sx;
transY = -sy;
}
//动画的Transformation不为null
if (transformToApply != null) {
if (concatMatrix) {
if (useDisplayListProperties) {
displayList.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the
// transformation matrix,
// then redo the scroll translate to get the correct
// result.
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
//获取Transformation的alpha值
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}

if (!childHasIdentityMatrix && !useDisplayListProperties) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
}
}
return more;
}

View的这个draw方法同draw(Canvas)的那个方法不同,它只是被ViewGroupd的drawChild调用,在这个方法里,我们可以看到绘制动画的方法,首先通过getAnimation获取到我们在startAnimation中设置的动画,然后交给drawAnimation计算动画的Transformation,它被存放在transformToApply中,如果有动画执行那么transformToApply肯定不为null,这里concatMatrix未true表示动画会改变变换矩阵,比如ScaleAnimation会改变变换的Matrix,而AlpahAnimation不会改变,为true这种情况会对canvas做一些列变换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
///frameworks/base/core/java/android/view/View.java
private boolean drawAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null)
a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();
}

final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}

if (more) {
if (!a.willChangeBounds()) {
if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE
| ViewGroup.FLAG_ANIMATION_DONE)) == ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially
// offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
} else {
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);

// The child need to draw an animation, potentially offscreen,
// so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;

final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}

这个方法是补间动画的核心处理方法,它首先对动画进行初始化initialize,接下来通过getTransformation计算动画的相关信息,并返回more,代表动画是否完成。如果是true表示还有动画需要执行,那么在最后会通过parent的invalidate重新发起绘制进行下一帧的绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
 public boolean getTransformation(long currentTime, Transformation outTransformation,
float scale) {
mScaleFactor = scale;
return getTransformation(currentTime, outTransformation);
}

public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}

final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}

final boolean expired = normalizedTime >= 1.0f;
mMore = !expired;

if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}

if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}

final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}

if (expired) {
if (mRepeatCount == mRepeated) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}

if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}

mStartTime = -1;
mMore = true;

fireAnimationRepeat();
}
}

if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}

return mMore;
}

getTransformation会根据当前时间和动画的持续时间执行时间流逝的进度,该进度随时间的流逝均匀的增加,如果该进度在0和1之间那么说明动画还在执行,如果动画此时还未开始会通过fireAnimationStart回调动画开始的回调,随后通过getInterpolation根据进度和插值器计算插值,插值和时间流逝的进度区别在于,插值可以根据数学模型生成任意的值,而时间流逝的进度值是客观不可变的。补间动画正是基于计算的插值来计算下一帧的动画的。计算完插值后通过applyTransformation来计算动画。我们可以看看TranslateAnimation是如何基于此计算动画的。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);
}

TranslateAnimation都是基于interpolatedTime计算动画偏移。如水平方向的偏移
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime) 这里的interpolatedTime可以看作是插值器计算的动画进度,它从0f-1.0f。

完成这一步,就可以通过view的父view发起绘制动画的请求,这是通过parent.invalidate方法来发起的。

坚持原创技术分享,您的支持将鼓励我继续创作!